// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.CommandLine;
using Microsoft.DotNet.Cli.ToolPackage;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli.Utils.Extensions;
using Microsoft.Extensions.EnvironmentAbstractions;

namespace Microsoft.DotNet.Cli.Commands.Tool.List;

internal delegate IToolPackageStoreQuery CreateToolPackageStore(DirectoryPath? nonGlobalLocation = null);

internal class ToolListGlobalOrToolPathCommand(
    ParseResult result,
    CreateToolPackageStore createToolPackageStore = null,
    IReporter reporter = null) : CommandBase(result)
{
    public const string CommandDelimiter = ", ";
    private readonly IReporter _reporter = reporter ?? Reporter.Output;
    private readonly IReporter _errorReporter = reporter ?? Reporter.Error;
    private readonly CreateToolPackageStore _createToolPackageStore = createToolPackageStore ?? ToolPackageFactory.CreateToolPackageStoreQuery;

    public override int Execute()
    {
        var toolPathOption = _parseResult.GetValue(ToolListCommandParser.ToolPathOption);
        var packageIdArgument = _parseResult.GetValue(ToolListCommandParser.PackageIdArgument);

        PackageId? packageId = null;
        if (!string.IsNullOrWhiteSpace(packageIdArgument))
        {
            packageId = new PackageId(packageIdArgument);
        }

        DirectoryPath? toolPath = null;
        if (!string.IsNullOrWhiteSpace(toolPathOption))
        {
            if (!Directory.Exists(toolPathOption))
            {
                throw new GracefulException(
                    string.Format(
                        CliCommandStrings.ToolListInvalidToolPathOption,
                        toolPathOption));
            }

            toolPath = new DirectoryPath(toolPathOption);
        }

        var packageEnumerable = GetPackages(toolPath, packageId);

        var formatValue = _parseResult.GetValue(ToolListCommandParser.ToolListFormatOption);
        if (formatValue is ToolListOutputFormat.json)
        {
            PrintJson(packageEnumerable);
        }
        else
        {
            PrintTable(packageEnumerable);
        }

        if (packageId.HasValue && !packageEnumerable.Any())
        {
            // return 1 if target package was not found
            return 1;
        }
        return 0;
    }

    public IEnumerable<IToolPackage> GetPackages(DirectoryPath? toolPath, PackageId? packageId)
    {
        return [.. _createToolPackageStore(toolPath).EnumeratePackages()
            .Where((p) => PackageHasCommand(p) && PackageIdMatches(p, packageId))
            .OrderBy(p => p.Id)];
    }

    internal static bool PackageIdMatches(IToolPackage package, PackageId? packageId)
    {
        return !packageId.HasValue || package.Id.Equals(packageId);
    }

    private bool PackageHasCommand(IToolPackage package)
    {
        try
        {
            // Attempt to read the command
            // If it fails, print a warning and treat as no commands
            return package.Command is not null;
        }
        catch (Exception ex) when (ex is ToolConfigurationException)
        {
            _errorReporter.WriteLine(
                string.Format(
                    CliCommandStrings.ToolListInvalidPackageWarning,
                    package.Id,
                    ex.Message).Yellow());
            return false;
        }
    }

    private void PrintTable(IEnumerable<IToolPackage> packageEnumerable)
    {
        var table = new PrintableTable<IToolPackage>();

        table.AddColumn(
            CliCommandStrings.ToolListPackageIdColumn,
            p => p.Id.ToString());
        table.AddColumn(
            CliCommandStrings.ToolListVersionColumn,
            p => p.Version.ToNormalizedString());
        table.AddColumn(
            CliCommandStrings.ToolListCommandsColumn,
            p => p.Command.Name.ToString());

        table.PrintRows(packageEnumerable, l => _reporter.WriteLine(l));
    }

    private void PrintJson(IEnumerable<IToolPackage> packageEnumerable)
    {
        var jsonData = new VersionedDataContract<ToolListJsonContract[]>()
        {
            Data = [.. packageEnumerable.Select(p => new ToolListJsonContract
            {
                PackageId = p.Id.ToString(),
                Version = p.Version.ToNormalizedString(),
                Commands = [p.Command.Name.Value]
            })]
        };
        var jsonText = System.Text.Json.JsonSerializer.Serialize(jsonData, JsonHelper.NoEscapeSerializerOptions);
        _reporter.WriteLine(jsonText);
    }
}
